package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.service.IHotelService;
import cn.itcast.hotel.vo.request.PageRequestVo;
import cn.itcast.hotel.vo.response.PageResultVo;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FieldValueFactorFunctionBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;

import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;

@Slf4j
@SuppressWarnings("ALL")
@Service("hotelService")
public class HotelService extends ServiceImpl<HotelMapper, Hotel>  {

    @Autowired
    private RestHighLevelClient client;

    /**
     * 分页查询实现
     *
     * @param vo
     * @return
     */
    @Override
    public PageResultVo getHotelList(PageRequestVo vo) {

        //设置请求对象
        SearchRequest request = new SearchRequest("hotel");

        PageRequestVo pageRequestVo = buildBasicQuery(vo, request);

        request.source().from(pageRequestVo.getPage()).size(pageRequestVo.getSize());
        SearchResponse response;
        try {
            response = client.search(request, RequestOptions.DEFAULT);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return parseInfo(response);

    }

    /**
     * 获取酒店星级、城市等信息
     *
     * @param vo
     * @return
     */
    @Override
    public Map<String, List<String>> getHotelInfos(PageRequestVo vo) {

        try {
            //获取查询请求对象
            SearchRequest request = new SearchRequest("hotel");

            //基础查询条件填充
            buildBasicQuery(vo, request);

            //将结果进行桶聚合分组-->结果list请求中已经有，这里不需要结果，长度设置为0
            request.source().size(0);

            //设置分组条件DSL
            builderAgg(request);

            //发送SDL
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //结果解析
            return parseAggregation(response, "brand","city","starName");
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        return null;
    }

    /**
     * 城市、星级信息等解析
     * @param response
     * @param aggNames
     * @return
     */
    private HashMap<String, List<String>> parseAggregation(SearchResponse response, String... aggNames) {

        HashMap<String, List<String>> map = new HashMap<>();
        if (aggNames == null && aggNames.length <= 0) {
            return map;
        }
        //聚合结果解析
        Aggregations agg = response.getAggregations();

        for (String name : aggNames) {

            ArrayList<String> list = new ArrayList<>();

            //根据名称解析结果
            Terms terms = agg.get(name);

            if (terms!=null){
                //获取桶结果
                List<? extends Terms.Bucket> buckets = terms.getBuckets();

                if (buckets!=null&&buckets.size()>0){
                    buckets.forEach(b -> {
                        list.add(b.getKeyAsString());
                    });
                }
                map.put(name, list);
            }
        }
        return map;
    }

    /**
     * 设置分组条件DSL
     *
     * @param request
     */
    private void builderAgg(SearchRequest request) {
        request.source().aggregation(AggregationBuilders.terms("brand")
                .field("brand")
                .size(20));
        request.source().aggregation(AggregationBuilders.terms("city")
                .field("city")
                .size(20));
        request.source().aggregation(AggregationBuilders.terms("starName")
                .field("starName")
                .size(20));
    }

    /**
     * 设置查询条件
     *
     * @param vo
     * @param request
     * @return
     */
    private PageRequestVo buildBasicQuery(PageRequestVo vo, SearchRequest request) {

        //参数判断
        boolean flag = vo == null;

        //获取默认对象
        PageRequestVo pageRequestVo = PageRequestVo.refreshPage(vo);

        BoolQueryBuilder bool = new BoolQueryBuilder();
        //城市
        if (StringUtils.isNotEmpty(pageRequestVo.getCity())) {
            bool.filter(QueryBuilders.termQuery("city", pageRequestVo.getCity()));
        }

        //品牌
        if (StringUtils.isNotEmpty(pageRequestVo.getBrand())) {
            bool.filter(QueryBuilders.termQuery("brand", pageRequestVo.getBrand()));
        }

        //星级
        if (StringUtils.isNotEmpty(pageRequestVo.getStarName())) {
            bool.filter(QueryBuilders.termQuery("starName", pageRequestVo.getStarName()));
        }

        //价格区间
        if (pageRequestVo.getMaxPrice() != null && pageRequestVo.getMinPrice() != null) {
            bool.filter(QueryBuilders.rangeQuery("price").gte(pageRequestVo.getMinPrice()).lte(pageRequestVo.getMaxPrice()));
        }

        //如果参数为空，走默认值查询
        if (flag || StringUtils.isEmpty(pageRequestVo.getKey())) {
            bool.must(QueryBuilders.matchAllQuery());
        } else {
            bool.must(QueryBuilders.matchQuery("all", pageRequestVo.getKey()));
            request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
        }

        //根据距离排序
        if (StringUtils.isNotEmpty((pageRequestVo.getLocation()))) {
            request.source().sort(SortBuilders
                    .geoDistanceSort("location", new GeoPoint(pageRequestVo.getLocation()))
                    .order(SortOrder.ASC)
                    .unit(DistanceUnit.KILOMETERS));
        }

        //根据评分升序排序
        if (StringUtils.isNotEmpty(pageRequestVo.getSortBy()) && "score".equals(pageRequestVo.getSortBy())) {
            request.source().sort("score", SortOrder.DESC);
        }

        //根据价格降序排序
        if (StringUtils.isNotEmpty(pageRequestVo.getSortBy()) && "price".equals(pageRequestVo.getSortBy())) {
            request.source().sort("price", SortOrder.ASC);
        }

        //将level字段参与结果
//        bool.should(QueryBuilders.existsQuery("level"));
        //设置排名，根据算分设置level排名
        FieldValueFactorFunctionBuilder functionBuilder = ScoreFunctionBuilders.fieldValueFactorFunction("level").modifier(FieldValueFactorFunction.Modifier.LN1P).factor(1.5F);

        //根据字段确认是否为付费用户，前端特殊展示
        FunctionScoreQueryBuilder.FilterFunctionBuilder filterFunctionBuilder = new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                QueryBuilders.termQuery("isAD", true),
                functionBuilder
        );
        FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = {filterFunctionBuilder};
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                bool, filterFunctionBuilders
        );

        //将所有set的条件给请求对象赋值
        request.source().query(functionScoreQuery);
        return pageRequestVo;
    }

    /**
     * 结果解析
     *
     * @param response
     * @return
     */
    private PageResultVo parseInfo(SearchResponse response) {
        //结果解析
        SearchHits hits = response.getHits();

        //获取总条数
        long count = hits.getTotalHits().value;

        //获取结果集合
        SearchHit[] hi = hits.getHits();

        ArrayList<HotelDoc> list = new ArrayList<>();

        //遍历解析
        Arrays.stream(hi).forEach(h -> {
            //获取行文档数据
            HotelDoc hotelDoc = JSON.parseObject(h.getSourceAsString(), HotelDoc.class);
            list.add(hotelDoc);

            //配置高亮展示
            Map<String, HighlightField> map = h.getHighlightFields();

            //参数不为空则获取高亮字段set
            if (map != null && map.size() > 0) {

                //搜索结果只有name字段展示，所以将name字段中包含关键字的设置为高亮
                HighlightField name = map.get("name");

                if (name != null) {
                    hotelDoc.setName(name.getFragments()[0].string());
                    list.add(hotelDoc);
                }
            }

            //获取排序后的距离，如果不为空，set
            if (h.getSortValues().length > 0) {
                Arrays.stream(h.getSortValues()).forEach(hotelDoc::setDistance);
            }

        });
        return new PageResultVo(response.getHits().getTotalHits().value, list);
    }
}
